Skip to content

Fix #3475: Emit 'true ? null : new { ... }' for null of anonymous type#3866

Open
siegfriedpammer wants to merge 1 commit into
masterfrom
fix-3475-anonymous-type-null
Open

Fix #3475: Emit 'true ? null : new { ... }' for null of anonymous type#3866
siegfriedpammer wants to merge 1 commit into
masterfrom
fix-3475-anonymous-type-null

Conversation

@siegfriedpammer

@siegfriedpammer siegfriedpammer commented Jul 5, 2026

Copy link
Copy Markdown
Member

Fixes #3475.

Problem: Test(true ? null : new { A = 1, B = 2 }) compiles to a bare ldnull passed to Test<<>f__AnonymousType0<int, int>>. The type argument cannot be written explicitly (anonymous types cannot be named), so ILSpy silently dropped the type argument list and emitted Test(null), which no longer compiles (CS0411).

Approach: when type inference fails and the type arguments contain anonymous types, CallBuilder now rewrites null-literal arguments of anonymous type as true ? null : new { A = default(int), B = default(int) } -- a conditional expression whose never-taken branch creates an instance of the anonymous type. This is the minimal C# expression that produces a null value of an anonymous type, and it makes the type arguments inferable again. Roslyn folds the constant conditional back to ldnull, so the output round-trips.

The instance expression is not assembled by hand: a synthesized newobj instruction with default.value arguments (nested newobj for directly nested anonymous types) is run through the existing translation pipeline, which already renders anonymous-type creations as object-initializer syntax with exactly-typed values (default(int), (string)null, ...). Exact types matter because anonymous-type property types are inferred from the initializer values. The original property values are not recoverable (the IL contains only ldnull) and are never evaluated.

Anonymous types that occur inside other constructed types (e.g. List<T> or arrays of an anonymous type) cannot be expressed without naming the type; those keep the previous output.

Verification: new round-trip cases in the AnonymousTypes pretty test, covering the plain, string-property, and nested cases (green in all 12 configurations runnable on Linux; mcs/legacy-csc configurations need Windows CI); full decompiler suite passes; the issue's exact reproduction decompiles to compilable code that prints the same anonymous type name at runtime as the original assembly.

🤖 Generated with Claude Code

@siegfriedpammer siegfriedpammer force-pushed the fix-3475-anonymous-type-null branch 2 times, most recently from cb87553 to 4345bfe Compare July 6, 2026 12:50
When a null literal is the only argument a generic type argument could
be inferred from, and that type argument is an anonymous type, ILSpy
used to drop the type arguments entirely (they cannot be written
explicitly), producing 'Test(null)', which no longer compiles
(CS0411). A conditional expression whose never-taken branch creates an
instance of the anonymous type is the minimal C# expression that gives
a null value that type, so the argument now carries enough information
for type inference.

The instance expression is obtained by translating a synthesized
'newobj' with 'default.value' arguments through the existing pipeline
rather than assembling syntax by hand; the property values are typed
defaults, since the IL only contains ldnull. Anonymous types occurring
inside other constructed types (e.g. arrays) stay unexpressible and
keep the previous output.

Assisted-by: Claude:claude-fable-5:Claude Code
@siegfriedpammer siegfriedpammer force-pushed the fix-3475-anonymous-type-null branch from 4345bfe to 38203f8 Compare July 6, 2026 16:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

null of anonymous type

1 participant